/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.handlers.commands;

import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.handlers.commands.CommandHandlersUtil.ValidationException;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XREDeleteCommand;
import com.hercules.android.receiver.model.commands.XRENewCommand;
import com.hercules.android.receiver.model.commands.XRESetCommand;
import com.hercules.android.receiver.model.commands.XRETargetCommand;
import com.hercules.android.receiver.model.objects.XREFont;
import com.hercules.android.receiver.model.objects.XREHTMLText;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XREResource;
import com.hercules.android.receiver.model.objects.XREStylesheet;
import com.hercules.android.receiver.model.objects.XREText;
import com.hercules.android.receiver.model.objects.XRETextInput;
import com.hercules.android.receiver.model.objects.XREView;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

/**
 * This is the default implementation used to find views affected by command.
 * <p>
 * <b>Thread Safety:</b> This class is not thread safe since it has mutable fields. But if they are not changed after
 * first configured, then this class is used in a thread safe manner.
 *
 * @author liuliquan, 7realm
 * @author 7realm
 * @version 1.0
 */
public class DefaultXREViewFinderUtility implements XREViewFinderUtility {
    /**
     * The logger used to perform logging.
     * <p>
     * Initial value is null. Can be any value. If it is null then no logging will be performed. It has getter and
     * setter.
     */
    private Logger logger = Logger.getLogger(DefaultXREViewFinderUtility.class.getSimpleName());

    /**
     * Default empty constructor.
     */
    public DefaultXREViewFinderUtility() {
        // empty
    }

    /**
     * Get the affected view by the given command.
     *
     * @param command the XRECommand which affects the view
     * @param receiver the receiver for the XRE application
     * @return the list of affected views. May be empty if no view affected.
     * @throws IllegalArgumentException if given command or receiver is null
     * @throws XREViewFinderUtilityException if any error occurs while finding the affected views
     */
    public List<XREView> getAffectedViews(XRECommand command, AndroidReceiver receiver)
        throws XREViewFinderUtilityException {
        String methodName = "getAffectedViews";
        LogUtil.enter(logger, this, methodName, command, receiver);
        try {
            // check arguments
            CommandHandlersUtil.checkNotNull(command, "command");
            CommandHandlersUtil.checkReceiver(receiver);

            // create an empty result list of XREView
            List<XREView> result = new ArrayList<XREView>();

            // if process command
            if (command instanceof XREDeleteCommand || command instanceof XRESetCommand) {
                processSetAndDeleteCommands(receiver, result, (XRETargetCommand) command);
            } else if (command instanceof XRENewCommand) {
                processNewCommand(receiver, result, (XRENewCommand) command);
            }
            return LogUtil.exit(logger, this, methodName, result);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(logger, this, methodName, e);
        } catch (ValidationException e) {
            throw LogUtil.error(logger, this, methodName, new XREViewFinderUtilityException(e.getMessage()));
        } catch (XREViewFinderUtilityException e) {
            throw LogUtil.error(logger, this, methodName, e);
        }
    }

    /**
     * Gets the logger used to perform logging.
     *
     * @return the logger used to perform logging
     */
    public Logger getLogger() {
        return logger;
    }

    /**
     * Sets the logger used to perform logging.
     *
     * @param logger the logger used to perform logging
     */
    public void setLogger(Logger logger) {
        this.logger = logger;
    }

    /**
     * Process {@link XRESetCommand} or {@link XREDeleteCommand} command.
     *
     * @param receiver the receiver for the XRE application
     * @param result the list of affected views
     * @param targetCommand the command to process
     * @throws XREViewFinderUtilityException if XREObject is not found by command.targetId
     */
    @SuppressWarnings("boxing")
    private static void processSetAndDeleteCommands(AndroidReceiver receiver, List<XREView> result,
        XRETargetCommand targetCommand) throws XREViewFinderUtilityException {

        // get affected XREObject
        int targetId = targetCommand.getTargetId();
        XREObject affectedXREObject = receiver.getApplicationData().getElementsXREIdToObject().get(targetId);
        checkObject(affectedXREObject, "receiver.applicationData.elementsXREIdToObject[" + targetId + "]");

        if (affectedXREObject instanceof XREView) {
            XREView affectedXREView = (XREView) affectedXREObject;
            result.add(affectedXREView);

            if (targetCommand instanceof XRESetCommand) {
	            List<XREResource> affectedResources = getAssociatedResources(affectedXREView, receiver);
	            for (XREResource affectedResource : affectedResources) {
	            	affectedResource.getAffectedViewIDs().add(affectedXREView.getId());
	            }
            } else {
            	List<XREResource> affectedResources = getAssociatedResources(affectedXREView, receiver);
            	for (XREResource affectedResource : affectedResources) {
	            	affectedResource.getAffectedViewIDs().remove(affectedXREView.getId());
	            }
            }
        } else if (affectedXREObject instanceof XREResource) {
        	XREResource affectedXREResource = (XREResource) affectedXREObject;

        	Iterator<Integer> pos = affectedXREResource.getAffectedViewIDs().iterator();
        	while (pos.hasNext()) {
        		int viewID = pos.next();
        		XREView affectedView = (XREView) receiver.getApplicationData().getElementsXREIdToObject().get(viewID);
        		if (affectedView != null) {
        			result.add(affectedView);
        		} else {
        			pos.remove();
        		}
        	}
        }
    }

    /**
     * Process {@link XRENewCommand} command.
     *
     * @param receiver the receiver for the XRE application
     * @param result the list of affected views
     * @param newCommand the command to process
     * @throws XREViewFinderUtilityException if XREObject is not found by newCommand.id
     */
    @SuppressWarnings("boxing")
    private static void processNewCommand(AndroidReceiver receiver, List<XREView> result, XRENewCommand newCommand)
        throws XREViewFinderUtilityException {
        // get affected XREObject
        XREObject affectedXREObject = receiver.getApplicationData().getElementsXREIdToObject().get(newCommand.getId());
        checkObject(affectedXREObject, "receiver.applicationData.elementsXREIdToObject[" + newCommand.getId() + "]");

        // if view has associated resource then add it
        if (affectedXREObject instanceof XREView) {
            XREView affectedXREView = (XREView) affectedXREObject;
            // AndroidAssembly1 always paint if view has parent
            if (affectedXREView.getParent() != null) {
                result.add(affectedXREView);
            }
            
            List<XREResource> affectedResources = getAssociatedResources(affectedXREView, receiver);
            for (XREResource affectedResource : affectedResources) {
            	affectedResource.getAffectedViewIDs().add(affectedXREView.getId());
            }
        }
    }

    /**
     * Check whether given XREResource is associated by given XREView.
     *
     * @param xreView the XREView to be checked whether it associates the given XREResource
     * @param xreResource the XREResource to be checked whether it is associated by the given XREView
     * @return true if given XREResource is associated by given XREView; False otherwise.
     */
    private static List<XREResource> getAssociatedResources(XREView xreView, AndroidReceiver receiver) {
    	List<XREResource> result = new ArrayList<XREResource>();
    	
        // get associated resource of xreView
        XREResource assignedResource = xreView.getResource();

        if (assignedResource != null) {
        	result.add(assignedResource);
        	
            if (assignedResource instanceof XREText) {
            	XREFont font = ((XREText) assignedResource).getFont();
            	result.add(font);
            } else if (assignedResource instanceof XREHTMLText) {
            	XREStylesheet stylesheet = ((XREHTMLText) assignedResource).getStylesheet();
            	result.add(stylesheet);
            } else if (assignedResource instanceof XRETextInput) {
            	XREFont font = ((XRETextInput) assignedResource).getFont();
            	result.add(font);
            }
        }
        
        XREResource maskImage = xreView.getMask();
        if (maskImage != null) {
        	result.add(maskImage);
        }
        
        return result;
    }
    
    /**
     * Checks if given {@link Object} is not null.
     *
     * @param obj the {@link Object} to check
     * @param name the name of the {@link Object} to check
     * @throws XREViewFinderUtilityException if given object us null
     */
    private static void checkObject(Object obj, String name) throws XREViewFinderUtilityException {
        if (obj == null) {
            throw new XREViewFinderUtilityException("Value of '" + name + "' should not be null.");
        }
    }
}